/*
 @Name: validate插件
 @description：该验证控件大部分参考validform组件，越写越感觉和它一样^-^
 @Author: ray
 @License: MIT
*/
; (function (window, $, undef) {
    "use strict";

    var plugName = "validate",
        isRayui = window.rayui && rayui.define,
        DealClass = function ($form, options) {//$form只能是一个，不能为多个
            var form = $form[0];
            //避免重复
            if (form.validinit) return this;
            form.validinit = true;
            form.validate_ajax_count = 0;

            this.form = form;
            //一个页面就只有一个datatype和tipMsg
            options.dataType && $.extend(DealClass.dataType, options.dataType);
            options.tipMsg && $.extend(true, DealClass.tipMsg, options.tipMsg);

            options = form.validateOpt = $.extend(true, {}, DealClass.option, options);
            options.rightClass === true && (options.rightClass = "validate_right");
            options.wrongClass === true && (options.wrongClass = "validate_wrong");

            //创建提示标签
            DealClass.utils.crtTipDom.call(form);

            //绑定blur事件
            $form.on("blur", "[datatype]", function () {
                if (this.hasAttribute("recheck")) $(this).data("visited", true);
                DealClass.utils.checkOne.call(this, true, $form);
            });

            //recheck绑定
            $form.find("[recheck]").each(function () {
                var $this = $(this), name = $(this).attr("recheck"),
                    $rechk = $form.find('[name="' + name + '"]');

                $rechk.blur(function () {
                    //只有当this访问过才会验证
                    $this.data("visited") && $this.blur();
                });
            });

            //绑定提交按钮事件
            options.btnSubmit && $form.find(options.btnSubmit).on("click." + plugName, function () {
                $form.submit();
                return false;
            });

            //绑定重置按钮事件
            $form.find("[type='reset']").add($form.find(options.btnReset)).on("click." + plugName, function () {
                DealClass.utils.resetForm.call($form);
            });

            //绑定回车事件
            options.enterNext && DealClass.utils.registerEnterKey.call(form);

            //表单提交
            $form.submit(function () {
                var flag = DealClass.utils.submitForm.call(form, false);
                return flag === false ? false : true;
            });

            //input type=number maxlength支持
            $form.find("input[type=number][maxlength]").each(function () {
                var max = $(this).prop("maxlength");
                max > 0 && $(this).attr("oninput", "if(value.length>" + max + ")value=value.slice(0," + max +")");
            });

            return this;
        };

    DealClass.option = {
        tipType: 1,//1：当前元素next；2：当前元素siblings；3：当前元素父级next；4：当前元素父级siblings；function()
        ignoreHidden: false,//是否忽略隐藏标签的验证
        checkingImg: true,//是否显示检查中等待动画图片，true|false|url，全局，每个检查元素可通过自己的属性checkingimg修改
        checkingTipDelay: 300,//检查中提示延时显示时间，数值（单位毫秒），默认300，只要用于ajax校验，通过此参数可以优化体验，只有在ajax异步时有效
        rightClass: true,//成功样式，true|false|css字符串，默认true
        wrongClass: true,//失败样式，true|false|css字符串，默认true
        hideSucTip: false,//当成功是隐藏提示，true|false，默认false
        showAllError: false,//是否显示全部错误，true|false，默认false，当出现错误时不再继续向下验证
        postOnce: false,//是否只允许提交一次
        enterNext: true,//当按下回车时自动下一个表单，true|false，默认true
        tipImg: true,//是否显示验证结果图片
        tipBottom: false,//tip提示是否在下边，true|false，默认false在右边，可以配合tipType参数实现任意位置展示
        ajaxPost: false,//是否ajax提交，默认false
        ajaxData_Code: "ret",//ajax返回值，0表示验证通过
        ajaxData_Msg: "msg"//ajax返回信息，提示语
    };

    DealClass.dataType = {
        "*": /[\w\W]+/,
        "*6-16": /^[\w\W]{6,16}$/,
        "n": /^\d+$/,
        "n6-16": /^\d{6,16}$/,
        "s": /^[\u4E00-\u9FA5\uf900-\ufa2d\w\.\s-]+$/,
        "s6-18": /^[\u4E00-\u9FA5\uf900-\ufa2d\w\.\s-]{6,18}$/,
        "p": /^\d{6}$/,
        "m": /^1(3|4|5|6|7|8|9)\d{9}$/,
        "e": /^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,
        "url": /^(\w+:\/\/)?\w+(\.\w+)+.*$/
    }
    DealClass.tipMsg = {
        dataTypeMsg: {
            "*": "不能为空",
            "*6-16": "请填写6到16位任意字符",
            "n": "请填写数字",
            "n6-16": "请填写6到16位数字",
            "s": "不能输入特殊字符",
            "s6-18": "请填写6到18位字符",
            "p": "邮政编码格式不正确",
            "m": "手机号码格式不正确",
            "e": "邮箱地址格式不正确",
            "url": "网址格式不正确"
        },
        undef: "datatype未定义",
        ajax404: "检验地址异常",
        nullMsg: "请输入信息",
        errorMsg: "输入信息有误",
        sucMsg: "信息通过验证",
        recheckMsg: "两次输入的内容不一致",
        checkingMsg: "正在检测信息…"
    }

    DealClass.utils = {
        //创建提示标签
        crtTipDom: function () {
            var form = this,
                options = form.validateOpt;

            //tipType:  1：当前元素next；2：当前元素siblings；3：当前元素父级next；4：当前元素父级siblings；function()
            if (typeof options.tipType === "function") return;//自定义显示错误不需要生成

            var $objs = $(this).find("[datatype]");
            $objs.each(function (a, b) {
                var $span = null, $o = $(b);
                if (options.tipType === 3 || options.tipType === 4) $o = $o.parent();
                if (options.tipType === 1 || options.tipType === 3) {
                    $span = $o.next();
                    if (!$span.is(".validate_checktip"))
                        $span = $('<' + (options.tipBottom ? "div" : "span") + ' class="validate_checktip" />').insertAfter($o);
                } else if (options.tipType === 2 || options.tipType === 4) {
                    $span = $o.siblings(".validate_checktip");
                    if ($span.length === 0)
                        $span = $('<' + (options.tipBottom ? "div" : "span") + ' class="validate_checktip" />').appendTo($o.parent());
                }
                b.$tip = $span;
            });
        },
        checkOne: function (show, $form) {
            //此处this为验证元素
            var othis = this,
                $this = $(othis),
                form = $form[0],
                options = form.validateOpt;
             
            //这里再次判断一下，防止js执行隐藏元素的blur方法引起的调用
            //排除addRule方法忽略元素
            if (options.ignoreHidden && $this.is(":hidden") || $this.data("dataIgnore")) return true;//开启跳过隐藏元素时默认通过

            //获取输入值
            var inputval = DealClass.utils.getValue.call(this, $form), rechkName = $this.attr("recheck");

            //如果值没有改变而且没有绑定recheck，直接返回结果
            if (othis.validate_lastval === inputval && !rechkName) return othis.validate_result.passed;
            othis.validate_lastval = inputval;

            //正则验证
            var ruleGroup = DealClass.utils.parseDatatype.call(this);
            var res = {};
            $.each(ruleGroup, function (a, b) {
                $.each(b, function (c, d) {
                    res = DealClass.utils.regexCheck.call(othis, d, inputval, $form);
                    if (!res.passed) return false;
                });
                if (res.passed) return false;
            });


            //recheck
            if (res.passed && rechkName) {
                var other = $form.find("input[name='" + rechkName + "']:first");
                if (inputval !== other.val()) {
                    res.passed = false;
                    res.info = DealClass.utils.getErrorMsg.call(othis, $form, "", true);
                }
            }

            //ajax check，如果验证失败无需继续
            var ajaxurl = $this.attr("ajaxurl");
            if (res.passed && ajaxurl) {

                var ajaxsetup = $.extend(true, {}, options.ajaxurl || {}), delay,
                    onComplete = function () {
                        othis.validate_ajax = null;
                        form.validate_ajax_count--;
                        if (ajaxsetup.async) {
                            clearTimeout(delay);
                            $this.prop("disabled", false);
                        }
                    };

                var localConfig = {
                    type: "POST",
                    url: ajaxurl,
                    async: true,
                    data: "param=" + encodeURIComponent($this.val()) + "&name=" + encodeURIComponent($(this).attr("name")),
                    success: function (data) {
                        onComplete();

                        //{ ret:-1, msg:"错误提示" }，ret=0表示成功
                        res.passed = data[options.ajaxData_Code] === 0;
                        res.info = data[options.ajaxData_Msg];
                        DealClass.utils.show.call(othis, res, $form);
                    },
                    error: function (data) {
                        //data可被用户进行修改，用户函数会第一时间拿到结果
                        onComplete();

                        //404错误
                        data["status"] === 404 && (data["responseText"] = DealClass.tipMsg.ajax404);

                        res.passed = data["responseText"] === "0" || data[options.ajaxData_Code] === 0;
                        res.info = data["responseText"] || data[options.ajaxData_Msg] || "ajax error";
                        DealClass.utils.show.call(othis, res, $form);
                    }
                }

                if (ajaxsetup.success) {
                    var userSucFunc = ajaxsetup.success;
                    ajaxsetup.success = function (data) {
                        //先调用用户函数，用户可以在此修改数据，这里逻辑和validform不一样
                        var tmpData = userSucFunc(data, $this);
                        localConfig.success(tmpData || data);
                    }
                }

                if (ajaxsetup.error) {
                    var userErrFunc = ajaxsetup.error;
                    ajaxsetup.error = function (data) {
                        //先调用用户函数，用户可以在此修改数据，这里逻辑和validform不一样
                        var tmpData = userErrFunc(data, $this);
                        localConfig.error(tmpData || data);
                    }
                }

                ajaxsetup = $.extend({}, localConfig, ajaxsetup, { dataType: "json" });
                othis.validate_ajax = $.ajax(ajaxsetup);
                form.validate_ajax_count++;

                res.passed = "ajax";
                res.info = DealClass.utils.getCheckingMsg.call(othis, $form);
                //如果是异步请求，显示验证中
                if (ajaxsetup.async) {
                    $this.prop("disabled", true);
                    delay = setTimeout(function () { DealClass.utils.show.call(othis, res, $form) }, options.checkingTipDelay);
                }

                othis.validate_result = res;
                return res.passed;
            }

            othis.validate_result = res;
            //如果验证失败，显示错误提示
            show && DealClass.utils.show.call(othis, res, $form);
            return res.passed;
        },
        regexCheck: function (datatype, gets, $form) {
            //此处this为验证元素
            //datatype有3中格式：自定义正则、自定义函数、预留，操作时分为两种：自定义、预留（自定义函数实际是加载到了预留里面）

            var res = { passed: false };//true false 字符串

            //如果值为空同时允许为空直接返回
            if (gets === "" && this.hasAttribute("ignore")) {
                res.passed = true;
                return res;
            }

            var othis = this,
                form = $form[0],
                //options = form.validateOpt,
                reg = /\/.+\//g, regstr, regparam, tip,
                regex = /^(.+?)(\d+)-(\d+)$/,
                regexTip = /(.*?)\d+(.+?)\d+(.*)/;//匹配规则：n4-6,s6-20

            //验证是否为空
            if (datatype === undef || datatype === "") {
                res.info = DealClass.tipMsg.undef;
                return res;
            }

            //自定义正则
            if (reg.test(datatype)) {
                regstr = datatype.match(reg)[0].slice(1, -1);//删除前后/
                regparam = datatype.replace(reg, "");
                var exp = new RegExp(regstr, regparam);

                res.passed = exp.test(gets);

                //函数
            } else if (typeof DealClass.dataType[datatype] === "function") {
                var back = DealClass.dataType[datatype].call(this, gets, form, DealClass.dataType);
                if (back === true || back === undef) {
                    res.passed = true;
                } else {
                    res.info = back;
                }
            } else {
                //自定义预留
                if (!DealClass.dataType.hasOwnProperty(datatype)) {
                    var mac = datatype.match(regex), temp;
                    if (!mac) {
                        res.info = DealClass.tipMsg.undef;
                    } else {
                        for (var name in DealClass.dataType) {
                            temp = name.match(regex);
                            if (!temp) { continue; }
                            if (mac[1] === temp[1]) {
                                var regxp = new RegExp("\\{" + temp[2] + "," + temp[3] + "\\}", "g");
                                //正则
                                regstr = DealClass.dataType[name].toString();
                                regparam = regstr.match(/\/[mgi]*/g)[1].replace("\/", "");
                                regstr = regstr.replace(/\/[mgi]*/g, "\/").replace(regxp, "{" + mac[2] + "," + mac[3] + "}").replace(/^\//, "").replace(/\/$/, "");
                                DealClass.dataType[datatype] = new RegExp(regstr, regparam);//

                                //错误提示
                                tip = DealClass.tipMsg.dataTypeMsg[name];
                                tip = tip.replace(regexTip, "$1" + mac[2] + "$2" + mac[3] + "$3");
                                DealClass.tipMsg.dataTypeMsg[datatype] = tip;
                                break;
                            }
                        }
                    }
                }

                if (!DealClass.dataType.hasOwnProperty(datatype)) {
                    res.info = DealClass.tipMsg.undef;
                } else if (DealClass.dataType[datatype] instanceof RegExp) {
                    res.passed = DealClass.dataType[datatype].test(gets);
                }
            }

            //验证不通过且为空时
            if (!res.passed && !res.info)
                res.info = DealClass.utils[gets === "" ? "getNullMsg" : "getErrorMsg"].call(othis, $form, datatype);

            return res;
        },
        check: function (show, selector, showAllError) {
            //此处this为form元素
            var form = this,
                $form = $(form),
                options = form.validateOpt,
                $objs = $form.find("[datatype]"),
                flag = true, isfocus = false;

            //指定选择器
            selector && ($objs = $form.find(selector));

            //跳过隐藏元素的检查
            if (options.ignoreHidden) $objs = $objs.find(":visible");

            $objs.each(function (a, b) {
                var res = DealClass.utils.checkOne.call(b, show, $form);
                flag = flag && res;
                //第一个失败的元素获取焦点
                !isfocus && !flag && ($(b).focus(), isfocus = true);
                if (!flag && !showAllError) return false;
                return true;
            });
            return flag;
        },
        parseDatatype: function () {
            //此处this为验证元素
            /*
                以下代码没有看懂，照搬的validform
                字符串里面只能含有一个正则表达式;
                Datatype名称必须是字母，数字、下划线或*号组成;
                datatype="/regexp/|phone|tel,s,e|f,e";
                ==>[["/regexp/"],["phone"],["tel","s","e"],["f","e"]];
                这里","分隔相当于逻辑运算里的"&&"； "|"分隔相当于逻辑运算里的"||"；不支持括号运算
            */
            if (this.validate_dateTypeRules) return this.validate_dateTypeRules;

            var datatype = $(this).attr("datatype"),
                reg = /\/.+?\/[mgi]*(?=(,|$|\||\s))|[\w\*-]+/g,
                dtype = datatype.match(reg),
                sepor = datatype.replace(reg, "").replace(/\s*/g, "").split(""),
                arr = [],
                m = 0;

            arr[0] = [];
            arr[0].push(dtype[0]);
            for (var n = 0; n < sepor.length; n++) {
                if (sepor[n] === "|") {
                    m++;
                    arr[m] = [];
                }
                arr[m].push(dtype[n + 1]);
            }

            this.validate_dateTypeRules = arr;
            return arr;
        },
        getValue: function ($form) {
            //此处this为验证元素
            var $this = $(this), inputval;

            if ($this.is(":radio")) {
                inputval = $form.find(":radio[name='" + $this.attr("name") + "']:checked").val();
                inputval = inputval || "";
            } else if ($this.is(":checkbox")) {
                inputval = "";
                $form.find(":checkbox[name='" + $this.attr("name") + "']:checked").each(function () {
                    inputval += $(this).val() + ',';
                });
                inputval.length > 0 && (inputval = inputval.substr(0, inputval.length - 1));
            } else {
                inputval = $this.val();
            }
            //如果含有vtrim属性则去除两边空白
            if (this.hasAttribute("vtrim")) inputval = $.trim(inputval);
            return inputval;
        },
        getCheckingMsg: function ($form) {
            var val = $(this).attr("checkingmsg");
            return val === undef ? DealClass.tipMsg.checkingMsg : val;
        },
        getSucMsg: function ($form) {
            var val = $(this).attr("sucmsg");
            return val === undef ? DealClass.tipMsg.sucMsg : val;
        },
        getNullMsg: function ($form) {
            var val = $(this).attr("nullmsg");
            return val === undef ? DealClass.tipMsg.nullMsg : val;
        },
        getErrorMsg: function ($form, datatype, recheck) {
            var val = $(this).attr("errormsg"),
                tipmsg = DealClass.tipMsg;

            if (val !== undef) return val;

            if (recheck)
                return tipmsg.recheckMsg;
            //去自定义里面去找
            val = tipmsg.dataTypeMsg[datatype];


            return val === undef ? tipmsg.errorMsg : val;
        },
        registerEnterKey: function () {
            var form = this,
                options = form.validateOpt,
                $objs = $(this).find("input[type=text],input[type=password],textarea");
            if ($objs.length === 0) return;

            function getNextVisibleObj(index) {
                if (index++ === $objs.length - 1) return null;
                var obj = $objs.eq(index);
                if (obj.is(":visible")) return obj;
                return getNextVisibleObj(index);
            }

            $objs.each(function (a, b) {
                $(b).on("keypress.rayui", function (e) {
                    if (e.keyCode === 13) {
                        var nextObj = getNextVisibleObj(a);
                        if (nextObj == null) {
                            $(this).blur();
                            options.btnSubmit ? ($(options.btnSubmit).click()) : ($(form).find(":submit").click());
                        } else {
                            nextObj.focus();
                            e.preventDefault();//屏蔽回车提交表单
                        }
                    }
                });
            });

        },
        show: function (res, $form) {
            if (this === undef) return;
            var othis = this,
                $this = $(othis),
                options = $form[0].validateOpt;

            //自定义显示错误不需要生成
            if (typeof options.tipType === "function") {
                //passed 0验证失败 1验证通过 2验证中
                res.passed = res.passed === "ajax" ? 2 : res.passed === false ? 0 : 1;
                options.tipType.call(othis, res.passed, res.info, $form[0]);
                return;
            }

            //清除
            DealClass.utils.hide.call(othis, $form);

            if (res.passed === "ajax") {
                //tip提示
                othis.$tip.addClass("validate_checking");
                //tip图片
                othis.$tip.append('<i class="ra layer-icon layer-loading-0 ra-spin"/>');
                //显示内容
                othis.$tip.append(res.info);
                return;
            }

            //文本框rightClass， wrongClass
            if (res.passed) {
                options.rightClass && $this.addClass(options.rightClass);
            } else {
                options.wrongClass && $this.addClass(options.wrongClass);
            }

            //成功时开启不提示则返回
            if (options.hideSucTip && res.passed) return;

            //成功提示语
            res.passed && !res.info && (res.info = res.info = DealClass.utils.getSucMsg.call(othis, $form));

            //tip提示
            othis.$tip.addClass(res.passed ? "validate_success" : "validate_error");
            //tip图片
            options.tipImg && othis.$tip.append('<i class="ra"/>');
            //显示内容
            othis.$tip.append(res.info);
        },
        hide: function ($form) {
            if (this === undef) return;
            var othis = this,
                $this = $(othis),
                options = $form[0].validateOpt;
            //tip提示
            othis.$tip.attr("class", "validate_checktip").html("");
            //文本框
            options.rightClass && $this.removeClass(options.rightClass);
            options.wrongClass && $this.removeClass(options.wrongClass);
        },
        submitForm: function (skipCheck, ajaxOptions) {
            ajaxOptions = ajaxOptions || {};
            //此处this为form元素
            var form = this,
                $form = $(form),
                options = form.validateOpt,
                hasCallback = typeof options.callback === "function";

            //表单只提交一次
            if (options.postOnce && form.validate_post) return false;


            //是否跳过验证
            if (!skipCheck) {
                //判断是否有ajax验证正在执行
                if (form.validate_ajax_count > 0) return false;

                //beforeCheck
                var beforeCheck = options.beforeCheck && options.beforeCheck.call(form);
                if (beforeCheck === false) return false;

                //判断是否有验证失败的元素
                if (!DealClass.utils.check.call(form, true, undef, options.showAllError)) return false;
            }

            //提交前
            var beforeSubmit = options.beforeSubmit && options.beforeSubmit.call(form);
            if (beforeSubmit === false) return false;

            //ajax提交
            if (options.ajaxPost) {
                var config = {
                    type: $form.attr("method") || "POST",
                    url: $form.attr("action"),
                    data: $form.serialize(),
                    success: function (data) {
                        form.validate_ajax = null;
                        hasCallback && options.callback.call(form, data);
                    },
                    error: function (data) {
                        form.validate_ajax = null;
                        hasCallback && options.callback.call(form, data);
                    }
                }
                $.extend(true, config, ajaxOptions);
                form.validate_ajax = $.ajax(config);
                form.validate_post = true;
                return false;
            }

            //form 直接提交
            form.validate_post = true;

            //修改url和method
            ajaxOptions.url && $form.attr("action", ajaxOptions.url);
            ajaxOptions.type && $form.attr("method", ajaxOptions.type);

            //callback回调
            return options.callback && options.callback.call(form);
        },
        resetForm: function () {
            var form = this, $form = $(form);
            form.reset && form.reset();
            form.validate_post = false;
            $form.find("[datatype]").removeData("dataIgnore").each(function () {
                this.validate_lastval = null;
                this.validate_ajax && this.validate_ajax.abort();
            });
            $form.find(".validate_checktip").attr("class", "validate_checktip").html("");
            $form.find(".validate_wrong").removeClass("validate_wrong");
            $form.find(".validate_right").removeClass("validate_right");
        }
    }

    DealClass.prototype = {
        ignore: function (selector) {
            var $form = $(this.form);
            selector = selector || "[datatype]";

            $form.find(selector).each(function () {
                $(this).data("dataIgnore", true);
                DealClass.utils.hide.call(this, $form);
            });
            return this;
        },
        unignore: function (selector) {
            var $form = $(this.form);
            selector = selector || "[datatype]";

            $form.find(selector).removeData("dataIgnore");
            return this;
        },
        check: function (show, selector) {
            return DealClass.utils.check.call(this.form, show, selector, true);
        },
        resetStatus: function () {
            this.form.validate_post = false;
            return this;
        },
        resetForm: function () {
            DealClass.utils.resetForm.call(this.form);
            return this;
        },
        submitForm: function (skipCheck, url) {
            //url可以是url，也可以是ajaxconfig对象
            typeof url === "string" && (url = { url: url });
            DealClass.utils.submitForm.call(this.form, skipCheck, url);
            return this;
        },
        abort: function () {
            var form = this.form;
            form.validate_ajax && form.validate_ajax.abort();
            return this;
        },
        abortAjaxUrl: function (selector) {
            var $form = $(this.form),
                $objs = $form.find("[datatype][ajaxurl]");

            selector && ($objs = $objs.find(selector));

            $objs.each(function (a, b) {
                b.validate_ajax && b.validate_ajax.abort();
                b.validate_lastval = null;
                DealClass.utils.hide.call(b, $form);
            });
        },
        /**
         *  手动调用显示
         * @param {any} info 显示信息
         * @param {any} type 类型 0失败 1成功 2检查中
         * @param {any} selector 检查控件
         */
        show: function (info, type, selector) {
            var $form = $(this.form);
            DealClass.utils.show.call(typeof selector === "string" ? $form.find(selector)[0] : $(selector)[0],
                {
                    passed: type === 1 ? true : type === 2 ? "ajax" : false,
                    info: info
                }, $form);
            return this;
        },
        hide: function (selector) {
            var $form = $(this.form);
            DealClass.utils.hide.call(typeof selector === "string" ? $(this.form).find(selector)[0] : $(selector)[0], $form);
            return this;
        }
    }


    var validate = {
        options: DealClass.option,
        dataType: DealClass.dataType,
        tipMsg: DealClass.tipMsg,
        valid: function (options) {
            var $elem = $(options.elem);
            if ($elem.length !== 1) {
                return "DOM对象不存在或存在多个对象";
            }
            return new DealClass($elem, options);
        }
    };

    isRayui ? rayui.define(function (exports) {
        exports(plugName, validate);
    }, rayui.jsAsync()) : function () {
        window.rayui = {
            plugName: validate
        };
    }();

})(window, jQuery);
